//////////////////////////////////////////////////////////////////////////
//
// GAM203
// Terrain
// Terrain.cpp
// 
// Grant Johnson
// Qantm College
// 27/10/2010
//
//////////////////////////////////////////////////////////////////////////

#include "Terrain.h"
#include <d3dx9math.h>

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "Shader.h"
#include "ShaderMgr.h"
#include "RenderParams.h"
#include <fstream>
#include "Renderer.h"

Terrain::Terrain(int a_terrainWidth, char* a_pHeightMapFileName)
: m_pHeightData(NULL)
, m_terrainWidth(a_terrainWidth)
, m_vertCount(0)
, m_indexCount(0)
, m_primCount(0)
, m_pVertData(NULL)
, m_pIndexData(NULL)
, m_pTex1(NULL)
, m_pTex2(NULL)
, m_pTex3(NULL)
{
	m_pTexFilename = new char[256];
	ZeroMemory(&m_mtl, sizeof(m_mtl));

	std::ifstream File(a_pHeightMapFileName, std::ios::binary);
	if(File.is_open())
	{
		File.seekg(0, std::ios::end);

		m_vertCount = File.tellg();
		m_gridVertX = (int)sqrt((float)m_vertCount);
		m_gridVertZ = (int)sqrt((float)m_vertCount);
		m_indexCount = ((m_gridVertX-1) * (m_gridVertX-1))*6;
		m_primCount = m_indexCount/3;

		File.seekg(0, std::ios::beg);

		m_pHeightData = new float[m_vertCount];

		//instead of looping through and getting all the data, it should loop through and only grab its own.
		for(int i=0; i<m_vertCount; ++i)
		{
			m_pHeightData[i] = (float)File.get();
			//reduce the overall height of the terrain
			m_pHeightData[i] /= 5.0f;
		}
		File.close();
	}
	m_aabb.GetMin() = D3DXVECTOR3(0, 0, 0);
	m_aabb.GetMax() = D3DXVECTOR3((float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertZ-1)*m_terrainWidth);
}
Terrain::Terrain(int a_terrainWidth, float* a_pHeightData, int a_numVerts)
: m_pHeightData(NULL)
, m_terrainWidth(a_terrainWidth)
, m_vertCount(0)
, m_indexCount(0)
, m_primCount(0)
, m_pVertData(NULL)
, m_pIndexData(NULL)
, m_pTex1(NULL)
, m_pTex2(NULL)
, m_pTex3(NULL)
{

	m_pTexFilename = new char[256];
	ZeroMemory(&m_mtl, sizeof(m_mtl));

	//Set the height data to the array passed in
	if(a_pHeightData)
	{
		m_pHeightData = a_pHeightData;
	}

	m_vertCount = a_numVerts;
	m_gridVertX = (int)sqrt((float)m_vertCount);
	m_gridVertZ = (int)sqrt((float)m_vertCount);
	m_indexCount = ((m_gridVertX-1) * (m_gridVertX-1))*6;
	m_primCount = m_indexCount/3;

	m_aabb.GetMin() = D3DXVECTOR3(0, 0, 0);
	m_aabb.GetMax() = D3DXVECTOR3((float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertX-1)*m_terrainWidth, (float)(m_gridVertZ-1)*m_terrainWidth);
}
Terrain::~Terrain()
{
	//Clean up data
	/*if(m_pHeightData)
	{
		delete m_pHeightData;
		m_pHeightData = NULL;
	}*/
	if(m_pVertData)
	{
		delete m_pVertData;
		m_pVertData = NULL;
	}
	if(m_pIndexData)
	{
		delete m_pIndexData;
		m_pIndexData = NULL;
	}
	if(m_pTex1)
	{
		m_pTex1->Release();
		m_pTex1 = NULL;
	}
	if(m_pTex2)
	{
		m_pTex2->Release();
		m_pTex2 = NULL;
	}
	if(m_pTex3)
	{
		m_pTex3->Release();
		m_pTex3 = NULL;
	}
	if(m_pTexFilename)
	{
		delete m_pTexFilename;
		m_pTexFilename = NULL;
	}
}
bool Terrain::CreateObject(IDirect3DDevice9* a_pDevice, char* a_pTexFileName)
{
	//Ensure there was a valid device passed in
	if(!a_pDevice)
	{
		return false;
	}

	D3DVERTEXELEMENT9 elements[] = 
	{
		{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
		{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
		{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
		D3DDECL_END()
	};

	if(FAILED(D3DXCreateMesh(m_primCount, m_vertCount, D3DXMESH_MANAGED, elements, a_pDevice, &m_pMesh)))
		return false;

	//Copy of the texture filename
	sprintf_s(m_pTexFilename, 256, "%s", a_pTexFileName);

	D3DXCreateTextureFromFile(a_pDevice, "media/Textures/sand.dds", &m_pTex1);
	D3DXCreateTextureFromFile(a_pDevice, "media/Textures/rock.dds", &m_pTex2);
	D3DXCreateTextureFromFile(a_pDevice, "media/Textures/grass.dds", &m_pTex3);

	//Setup the material
	m_mtl.Ambient = D3DXCOLOR(0.05f, 0.05f, 0.05f, 1.0f);
	m_mtl.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	m_mtl.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
	m_mtl.Specular = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
	m_mtl.Power = 0.1f;

//	SmoothTerrain();

	//Create terrain vertex data
	m_pVertData = new VertexData[m_vertCount];
	for(long posZ = 0; posZ < m_gridVertZ; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX; ++posX)
		{
			long vertIndex = (posZ*m_gridVertX)+posX;
			VertexData& vert = m_pVertData[vertIndex];

			vert.pos = D3DXVECTOR3((float)posX * m_terrainWidth, m_pHeightData[vertIndex], (float)posZ * m_terrainWidth);
			vert.u = (float)posX/m_gridVertX*2;
			vert.v = (float)posZ/m_gridVertZ*2;
		}
	}

	//Calculate the normals on the terrain for lighting
	//CalculateNormals();


	//Create terrain index data
	m_pIndexData = new unsigned short[m_indexCount];
	long curIndex = 0;

	//Minus one to loop through all the quads, not all the verts
	for(long posZ = 0; posZ < m_gridVertZ-1; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX-1; ++posX)
		{
			//Setup indices
			//top left
			m_pIndexData[curIndex] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+1] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);
			//top right
			m_pIndexData[curIndex+2] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)(posX+1);
			//top left 
			m_pIndexData[curIndex+3] = (unsigned short)(posZ*m_gridVertX)+(unsigned short)posX;
			//bottom left
			m_pIndexData[curIndex+4] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)posX;
			//bottom right
			m_pIndexData[curIndex+5] = (unsigned short)((posZ+1)*m_gridVertX)+(unsigned short)(posX+1);

			curIndex+=6;
		}
	}

	assert(curIndex == m_indexCount);

	//Copy vertex data into vertex buffer
	void* pLockedData = NULL;
	if(FAILED(m_pMesh->LockVertexBuffer(0, &pLockedData)))
		return false;
	memcpy(pLockedData, m_pVertData, sizeof(VertexData) * m_vertCount);
	if(FAILED(m_pMesh->UnlockVertexBuffer()))
		return false;
	pLockedData = NULL;

	//Copy index data into index buffer
	if(FAILED(m_pMesh->LockIndexBuffer(0, &pLockedData)))
		return false;
	memcpy(pLockedData, m_pIndexData, sizeof(unsigned short) * m_indexCount);
	if(FAILED(m_pMesh->UnlockIndexBuffer()))
		return false;
	pLockedData = NULL;

	return true;
}
void Terrain::Render(float a_dt, IDirect3DDevice9 *a_pDevice, const RenderParams& a_renderParams)
{
	if(a_pDevice)
	{
		const char* SHADER_NAME = "MultiTextureShader";
		Shader* pShader = ShaderMgr::GetInstance().Find(SHADER_NAME);
		if(pShader)
		{
			ID3DXEffect* pEffect = pShader->GetEffect();
			if(pEffect)
			{
				pEffect->SetTechnique("AdvRenderDiff1Spec1Dir1Omni1NoCol");

				//Set material global variables
				pEffect->SetValue("g_materialAmbientColor", &D3DXCOLOR(m_mtl.Ambient), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialDiffuseColor", &D3DXCOLOR(m_mtl.Diffuse), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialSpecularColor", &D3DXCOLOR(m_mtl.Specular), sizeof(D3DXCOLOR));
				pEffect->SetValue("g_materialEmissiveColor", &D3DXCOLOR(m_mtl.Emissive), sizeof(D3DXCOLOR));
				pEffect->SetFloat("g_materialSpecularPower", m_mtl.Power);
				pEffect->SetFloat("g_materialSpecularIntensity", 2.0f);

				//Set texture global variable
				pEffect->SetTexture("g_diffuseTexture1", m_pTex1);
				pEffect->SetTexture("g_diffuseTexture2", m_pTex2);
				pEffect->SetTexture("g_diffuseTexture3", m_pTex3);
				pEffect->SetTexture("g_specularTexture", m_pTex1);

				int index = 0;
				//Set up lighting global variables
				for(int lightId = 0; lightId < a_renderParams.GetLightCount(); ++lightId)
				{
					if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_DIRECTIONAL)
					{
						//Set lighting parameters
						//Note: Light direction is negated to optimise the dot product in 
						//shader calculations
						pEffect->SetValue("g_directionalDir", &(D3DXVECTOR3(a_renderParams.GetLight(lightId)->Direction) * -1.0f), sizeof(D3DXVECTOR3));
						pEffect->SetValue("g_directionalDiffuse", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Diffuse), sizeof(D3DXCOLOR));
						pEffect->SetValue("g_directionalSpecular", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Specular), sizeof(D3DXCOLOR));
					}
					else if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_POINT)
					{
						OmniArray[index].pos = a_renderParams.GetLight(lightId)->Position;
						OmniArray[index].range = a_renderParams.GetLight(lightId)->Range;
						OmniArray[index].diffuse = a_renderParams.GetLight(lightId)->Diffuse;
						OmniArray[index].specular = a_renderParams.GetLight(lightId)->Specular;
						++index;
					}
				}
				//Set the number of lights
				pEffect->SetValue("OmniLights", OmniArray, sizeof(OmniArray)*NUMLIGHTS); 

				pEffect->SetValue("g_fogColor", &D3DXCOLOR(0.3f, 0.35f, 0.4f, 1.0f), sizeof(D3DXCOLOR));

				pEffect->SetFloat("g_fogDistStart", 10);
				pEffect->SetFloat("g_fogDistEnd", 500);
				pEffect->SetValue("g_cameraPos", Renderer::GetInstance().GetActiveCamera()->GetEyePos(), sizeof(D3DXVECTOR3));

				pEffect->SetValue("g_totalAmbient", &D3DXCOLOR(a_renderParams.GetGlobalAmbient()), sizeof(D3DXCOLOR));

				//Set matrix global variables
				//Calculate a combined world*view*proj matrix for the vertex shader
				D3DXMATRIX worldViewProj = m_worldMatrix*a_renderParams.GetViewMatrix()*a_renderParams.GetProjMatrix();
				pEffect->SetMatrix("g_worldViewProj", &worldViewProj);
				pEffect->SetMatrix("g_worldMat", &m_worldMatrix);

				//a_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); 
				//Render with shader
				UINT passCount = 0;
				if(pEffect->Begin(&passCount, 0) == D3D_OK)
				{
					for(UINT passIndex = 0; passIndex < passCount; ++passIndex)
					{
						if(pEffect->BeginPass(passIndex) == D3D_OK)
						{
							//Draw the terrain
							m_pMesh->DrawSubset(0);
							pEffect->EndPass();
						}
					}
					pEffect->End();
				}
				a_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); 
			}
		}
	}
}
void Terrain::SetHeight(int a_index, float a_val)
{
	m_pHeightData[a_index] = a_val;
}
bool Terrain::CreateHeightMap()
{
	//Ensure the terrain the of an appropriate size
	/*assert(m_gridSizeX > 1 && m_gridSizeZ > 1);
	if(!(m_gridSizeX > 1 && m_gridSizeZ > 1))
	{
	return false;
	}

	//Create the height data
	m_pHeightData = new float[m_vertCount];
	if(!m_pHeightData)
	{
	return false;
	}
	//Random heights for verts in the grid
	for(int i=0; i<m_vertCount; ++i)
	{
	m_pHeightData[i] = rand()%15;
	}*/

	return true;
}
void Terrain::CalculateNormals()
{
	for(long posZ = 0; posZ < m_gridVertZ; ++posZ)
	{
		for(long posX = 0; posX < m_gridVertX; ++posX)
		{
			const long vertIndex = (posZ * m_gridVertX)+posX;
			D3DXVECTOR3 up,down,left,right;

			//if not top row
			if(posZ < m_gridVertZ - 1)
			{
				up = m_pVertData[((posZ+1)*m_gridVertX)+posX].pos;
				up -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&up, &up);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not bottom row
			if(posZ != 0)
			{
				down = m_pVertData[((posZ-1)*m_gridVertX)+posX].pos;
				down -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&down, &down);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not right most column
			if(posX < m_gridVertX - 1)
			{
				right = m_pVertData[(posZ*m_gridVertX)+(posX+1)].pos;
				right -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&right, &right);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//if not left most column
			if(posX != 0)
			{
				left = m_pVertData[(posZ*m_gridVertX)+(posX-1)].pos;
				left -= m_pVertData[vertIndex].pos;
				D3DXVec3Normalize(&left, &left);
			}
			else
			{
				//@SCOTTS HACK 
				m_pVertData[vertIndex].normal = D3DXVECTOR3(0,1,0);
				continue;
			}

			//cross products
			//up and left
			D3DXVECTOR3 temp1;
			D3DXVECTOR3 temp2;
			D3DXVec3Cross(&temp1, &down, &left);

			//cross products
			//down and right
			D3DXVec3Cross(&temp2, &up, &right);

			temp1 += temp2;

			//Normalize the new vector to give a unit length directional vector
			D3DXVec3Normalize(&temp1, &temp1);
			//Apply the normal
			m_pVertData[vertIndex].normal = temp1;
		}
	}
}


float Terrain::GetHeightAtPos(float a_x, float a_z)
{
	//Get the world matrix of the terrain chunk, used for an offset if needed
	D3DXMATRIX temp = GetWorldMatrix();

	//Convert from dwarfs world position into a grid position
	//Note: this is the col and row to his left
	int xIndex = (int)((a_x-temp._41)/m_terrainWidth);
	int zIndex = (int)((a_z-temp._43)/m_terrainWidth);

	//Get the heights of the 4 neighbouring points
	float height1 = m_pHeightData[(zIndex*m_gridVertX)+xIndex];
	float height2 = m_pHeightData[((zIndex+1)*m_gridVertX)+xIndex];

	float height3 = m_pHeightData[(zIndex*m_gridVertX)+(xIndex+1)];
	float height4 = m_pHeightData[((zIndex+1)*m_gridVertX)+(xIndex+1)];

	//Get neighbouring points
	D3DXVECTOR3 topLeft((float)xIndex*m_terrainWidth, height1, (float)zIndex*m_terrainWidth);
	D3DXVECTOR3 topRight((float)xIndex*m_terrainWidth, height2, (float)(zIndex+1)*m_terrainWidth);

	D3DXVECTOR3 bottomLeft((float)(xIndex+1)*m_terrainWidth, height3, (float)zIndex*m_terrainWidth);
	D3DXVECTOR3 bottomRight((float)(xIndex+1)*m_terrainWidth, height4, (float)(zIndex+1)*m_terrainWidth);

	//this will give us the difference between the top 2 vectors
	D3DXVECTOR3 diff1 = (topRight - topLeft);

	//to find the percentage between these points to lerp at, we use this:
	float percentage1 = ((a_z-temp._43) - topLeft.z)/m_terrainWidth; //get the distance between the left side and the player

	//lerp between the diff1 by percentage1
	D3DXVECTOR3 lerp1 = (diff1 * percentage1);
	lerp1.y += height1;

	//this will give us the distance vector between 2 points..
	D3DXVECTOR3 diff2 = (bottomRight - bottomLeft);

	//to find the percentage between these points to lerp at, we use this:
	float percentage2 = ((a_z-temp._43) - bottomLeft.z)/m_terrainWidth; //get the distance between the left side and the player

	//lerp between the diff2 by percentage2
	D3DXVECTOR3 lerp2 = (diff2 * percentage2);
	lerp2.y += height3;

	//now we want to lerp between lerp1 and lerp2
	D3DXVECTOR3 diff3 = (lerp2 - lerp1);

	//find the percentage
	float percentage3 = ((a_x-temp._41) - topLeft.x)/m_terrainWidth;

	D3DXVECTOR3 lerp3 = (diff3 * percentage3);
	lerp3.y += lerp1.y;

	return lerp3.y;
}